﻿using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using Framework.Modelo;

namespace Framework.CapaDeDatos.Convertores
{
    public static class ConvertorXml 
    {
        public static XmlDocument Obtener(IObjeto objeto)
        {
            XmlDocument resultado = null;
            if (objeto != null)
            {
                resultado = new XmlDocument();
                StringBuilder sXmlDocument = new StringBuilder();
                sXmlDocument.AppendFormat(@"<{0}>", objeto.GetType().FullName);
                AppendChilds(objeto, sXmlDocument);
                sXmlDocument.AppendFormat(@"</{0}>", objeto.GetType().FullName);
                resultado.LoadXml(sXmlDocument.ToString());
            }
            return resultado;
        }

        private static void AppendChilds(IObjeto objeto, StringBuilder sXmlDocument)
        {
            Collection<PropertyInfo> propiedades = Reflection.ObtenerPropiedades(objeto);
            foreach (PropertyInfo pi in propiedades)
            {
                object o = pi.GetValue(objeto, null);
                Type[] interfaces = o.GetType().GetInterfaces();
                if (((IList)interfaces).Contains(typeof(IObjeto)))
                {
                    AppendChilds((IObjeto)o, sXmlDocument);
                }
                else
                {
                    sXmlDocument.AppendFormat("<{0} Type=\"{1}\">", pi.Name, pi.PropertyType.FullName);
                    sXmlDocument.AppendFormat("{0}", o.ToString());
                    sXmlDocument.AppendFormat(@"</{0}>", pi.Name);
                }
            }
        }

        public static IObjeto Obtener(XmlDocument document)
        {
            IObjeto resultado = null;
            if (document != null)
            {
                resultado = ObtenerInstanciaDelObjeto(document.DocumentElement.Name);
                Object objeto = Reflection.ObtenerObjeto(document.FirstChild);
                foreach (XmlNode xn in document.FirstChild.ChildNodes)
                {
                    SetearPropiedad(resultado, xn);
                }
            }
            return resultado;
        }

        private static void SetearPropiedad(IObjeto resultado, XmlNode xn)
        {
            Collection<PropertyInfo> propiedades = Reflection.ObtenerPropiedades(resultado);
            if (xn != null)
            {
                Type tipo = ObtenerTipoPropiedad(xn);
                if (tipo == null)
                    tipo = ObtenerTipoPropiedadDelObjeto(xn, propiedades);
                if (tipo != null)
                {
                    Type[] interfaces = tipo.GetInterfaces();
                    if (((IList)interfaces).Contains(typeof(IObjeto)))
                    {
                        IObjeto objeto = Obtener(xn);
                        foreach (PropertyInfo pi in propiedades)
                        {
                            if (pi.PropertyType.FullName == tipo.FullName && pi.Name == xn.Name)
                            {
                                pi.SetValue(resultado, Convert.ChangeType(objeto, pi.PropertyType, CultureInfo.InvariantCulture), null);
                            }
                        }
                    }
                    else
                    {
                        foreach (PropertyInfo pi in propiedades)
                        {
                            if (pi.PropertyType.FullName == tipo.FullName && pi.Name.ToLowerInvariant() == xn.Name.ToLowerInvariant())
                            {
                                pi.SetValue(resultado, Convert.ChangeType(ConvertTo(xn.InnerText, pi.PropertyType), pi.PropertyType, CultureInfo.InvariantCulture), null);
                            }
                        }
                    }
                }
            }
        }

        private static Type ObtenerTipoPropiedadDelObjeto(XmlNode xn, Collection<PropertyInfo> propiedades)
        {
            Type resultado = null;
            foreach (PropertyInfo pi in propiedades)
            {
                if (xn.Name.ToLowerInvariant() == pi.Name.ToLowerInvariant())
                {
                    resultado = pi.PropertyType;
                }
            }
            return resultado;
        }

        public static object ConvertTo(object objeto, Type tipo)
        {
            object obj = null;
            switch (tipo.FullName)
            {
                case "System.Boolean":
                    obj = Convert.ToBoolean(objeto);
                    break;
                case "System.Byte":
                    obj = Convert.ToByte(objeto);
                    break;
                case "System.Char":
                    obj = Convert.ToChar(objeto);
                    break;
                case "System.DateTime":
                    obj = Convert.ToDateTime(objeto);
                    break;
                case "System.Decimal":
                    obj = Convert.ToDecimal(objeto);
                    break;
                case "System.Double":
                    obj = Convert.ToDouble(objeto);
                    break;
                case "System.Int16":
                    obj = Convert.ToInt16(objeto);
                    break;
                case "System.Int32":
                    obj = Convert.ToInt32(objeto);
                    break;
                case "System.Int64":
                    obj = Convert.ToInt64(objeto);
                    break;
                case "System.SByte":
                    obj = Convert.ToSByte(objeto);
                    break;
                case "System.Single":
                    obj = Convert.ToSingle(objeto);
                    break;
                case "System.String":
                    obj = Convert.ToString(objeto);
                    break;
                case "System.ToUInt16":
                    obj = Convert.ToUInt16(objeto);
                    break;
                case "System.ToUInt32":
                    obj = Convert.ToUInt32(objeto);
                    break;
                case "System.ToUInt64":
                    obj = Convert.ToUInt64(objeto);
                    break;
                case "System.Guid":
                    obj = new Guid(objeto.ToString());
                    break;
                case "System.Uri":
                    obj = new Uri(objeto.ToString());
                    break;
                default:
                    throw new NotImplementedException(String.Format("Falta implementar el Convert para el tipo {0}.", tipo.FullName));
            }
            return obj;
        }

        public static IObjeto Obtener(XmlNode xn)
        {
            IObjeto resultado = null;
            resultado = ObtenerInstanciaDelObjeto(xn.Name);
            foreach (XmlNode xn1 in xn.ChildNodes)
            {
                SetearPropiedad(resultado, xn1);
            }
            return resultado;
        }

        private static Type ObtenerTipoPropiedad(XmlNode xn)
        {
            Type resultado = null;
            foreach (XmlAttribute a in xn.Attributes)
            {
                if (a.Name == "Type")
                {
                    resultado = Type.GetType(a.Value);
                }
            }
            return resultado;
        }

        private static IObjeto ObtenerInstanciaDelObjeto(string fullTypeName)
        {
            String assemblyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Framework.Modelo.dll");
            Assembly assembly = Assembly.LoadFile(assemblyFile);
            foreach (Type type in assembly.GetTypes())
            {
                if (!type.IsClass || type.IsNotPublic)
                    continue;
                Type[] interfaces = type.GetInterfaces();
                if (((IList)interfaces).Contains(typeof(IObjeto)))
                {
                    if (type.FullName == fullTypeName)
                    {
                        return (IObjeto)Activator.CreateInstance(type);
                    }
                }
            }
            return null;
        }
    }
}
